import { _decorator, Node, Vec3, geometry, PhysicsSystem, SphereCollider, Prefab, instantiate }  from 'cc';

import { Game } from '../core/Game';
import { BaseComponent } from './BaseComponent';
import { BilliardGame } from './BilliardGame';
import { Vector3 } from '../framework/Vector3';
import { ColliderMask } from '../core/CommUtil';
import { SoundManager } from '../framework/SoundManager';
import { EventManager } from '../core/EventManager';
import { SendWebSocket } from '../websocket/SendWebSocket';
import { TableAtrribute } from '../core/TableAttribute';
import { NetHitMainBall } from '../core/WebSocketData';
import { WebFrameController } from '../user/WebFrameController';
import { Databus } from '../core/Databus';
const { ccclass, property } = _decorator;

const databus = Databus.Instance();

@ccclass('QiuGanGontroller')
export class QiuGanGontroller extends BaseComponent {
  private static ColliderBallMask = ColliderMask.BALL + ColliderMask.DESKEDGE + ColliderMask.DESKHOLE;
  
  // 球杆的预制件[默认，高端，奢华，完美]
  @property(Prefab)
  DefaultQiuGan: Prefab = null;
  @property(Prefab)
  HighLevelQiuGan: Prefab = null;
  @property(Prefab)
  LuxuryQiuGan: Prefab = null;
  @property(Prefab)
  PerfectQiuGan: Prefab = null;
  
  private qiuGan: Node = null;

  private whiteBall: Node = null;
  protected whiteBallCollider: SphereCollider = null; // 球刚体

  private virtualHitBall: Node = null;

  // 球杆对球心点/球杆端点
  private qiuganEndPoint: Node = null; // 球杆上的击球点
  private qiuganDefault: Node = null; // 球杆上对应白球中心位置

  // 加塞控制位置
  private spinOffsetPos = new Vec3(0, 0, 0);

  // y 保持水平桌面
  private defaultPointTmpPos: Vec3 = new Vec3();
  private endPointTmpPos: Vec3 = new Vec3();
  private forceValue: number = 0;

  // 白球到目标球的 瞄准线
  private aimLine: Node = null;
  // 碰撞后的反射线
  private reflectLine: Node = null;
  // 碰撞后的折射线(目标球的运行线路)
  private refractLine: Node = null;
  private refractLineDirection: Vec3 = new Vec3();
  // 是否已经开球
  private MaxForceValue: number = 100; // 最大力度
  private MaxBackDistance: number = 5;
  onLoad() {
    this.updateQiuGanByName(Game.Instance().getCurrentUserQiuGanName());

    this.whiteBall = this.node.getParent().getChildByName('MainBall').getChildByName('WhiteBall').getChildByName('BallSphereMain');
    this.whiteBallCollider = this.whiteBall.getComponent(SphereCollider);

    this.virtualHitBall = this.node.getParent().getChildByName('VirtualHitBall').getChildByName('BallSphere');

    EventManager.Instance().AddEventListener('QiuGanDirectionChanged', this, this.updateDirection);
    EventManager.Instance().AddEventListener('QiuGanForceChanged', this, this.updateForce);
    EventManager.Instance().AddEventListener('QiuGanForceEnded', this, this.startHitBall);
    EventManager.Instance().AddEventListener('WebQiuGanForceEnded', this, this.webStartHitBall);

    EventManager.Instance().AddEventListener('QiuGanPositionController', this, this.updateQiuGanPosition);

    EventManager.Instance().AddEventListener('HitBallHasChoosed', this, this.onAimToTargetBall);
    EventManager.Instance().AddEventListener('ExchangedUserHitBall', this, this.exchangedUserHitBall);

    EventManager.Instance().AddEventListener('ChangeOffsetSpinPosition', this, this.updateOffsetSpinPosition);

    EventManager.Instance().AddEventListener('StartStep', this, this.startStep);
    EventManager.Instance().AddEventListener('StartGame', this, this.startGame);
    EventManager.Instance().AddEventListener('HideQiuGanAimCommponent', this, this.hideQiuGanAimCommponent);
    EventManager.Instance().AddEventListener('ShowQiuGanAimCommponent', this, this.showQiuGanAimCommponent);
    this.showQiuGanAimCommponent('', {});
  }
  public updateQiuGanByName(ganName: string) {
    if (ganName === "HighLevelQiuGan") {
      this.qiuGan = instantiate(this.HighLevelQiuGan);
      this.updateQiuGan(this.qiuGan);
    } else if (ganName === "LuxuryQiuGan") {
      this.qiuGan = instantiate(this.LuxuryQiuGan);
      this.updateQiuGan(this.qiuGan);
    } else if (ganName === "PerfectQiuGan") {
      this.qiuGan = instantiate(this.PerfectQiuGan);
      this.updateQiuGan(this.qiuGan);
    } else { // DefaultQiuGan 或者异常值都用 DefaultQiuGan
      this.qiuGan = instantiate(this.DefaultQiuGan);
      this.updateQiuGan(this.qiuGan);
    }
    this.qiuganDefault = this.qiuGan.getChildByName('Default');
    this.qiuganEndPoint = this.qiuGan.getChildByName('Default').getChildByName('EndPoint');
    this.aimLine = this.qiuGan.getChildByName('AimLine');
    this.refractLine = this.qiuGan.getChildByName('RefractLine');
    this.reflectLine = this.qiuGan.getChildByName('ReflectLine');
  }
  public exchangedUserHitBall(name: string, data: any) {
    if (Game.Instance().battleCate.isFancyBattle()) {
      return;
    }
    if (Game.Instance().battleCate.isEndlessBattle()) {
      return;
    }
    const pu = Game.Instance().getPlayUserIndex(Game.Instance().side);
    this.updateQiuGanByName(pu.outlookAttribute.qiuGan.ganName);
  }
  update(dt: number) {
    this.drawAimLine();
  }
  public startGame(name: string, data: any) {
    this.spinOffsetPos.set(0, 0, 0);
    this.node.active = true;
    this.updateQiuGanByName(Game.Instance().getCurrentUserQiuGanName());
    this.showQiuGanAimCommponent('', {});
  }
  public startStep(name: string ,pos: number) {
    this.spinOffsetPos.set(0, 0, 0);
    this.node.active = true;
  }
  // 初始化球杆，瞄准一个目标球
  public onAimToTargetBall(name: string, targetPos: Vec3) {
    this.showAimCommponent();
    // 对准目标位置
    this.setDefaultWorldPosition(this.whiteBall.worldPosition);
    this.qiuganDefault.lookAt(targetPos);

    this.drawAimLine(); // 先draw一下hitPosition才准确
    EventManager.Instance().Emit('CameraAimPointDirection', {
      from: BilliardGame.Instance().getMainWhiteBall().worldPosition,
      to: BilliardGame.Instance().getCurrentHitPosition(),
      lookPoint: targetPos,
      aimFlag: 1, cameraHeight: 1.5,
      mbPos: BilliardGame.Instance().getMainWhiteBall().worldPosition,
    });
    // 只有自己击球的时候才发送网络消息
    if (Game.Instance().isSelftHitBall()) {
      new SendWebSocket().sendAimLine(0,
        this.getEndPointWorldPosition(), this.getDefaultWorldPosition(), this.qiuganDefault.getRotation()
      );

      // 判断是否为禁止击打的球
      if (BilliardGame.Instance().isForbidTargetBall()) {
        SoundManager.Instance().playForbidHit();
      }
    }
  }
  public updateQiuGanPosition(name: string, qg: any) {
    this.setDefaultWorldPosition(qg.defaultPoint);
    this.qiuganDefault.setRotation(qg.qiuganRotation);
    this.drawAimLine(); // 先draw一下hitPosition才准确
    this.cameraAimPointDirection(1, 1.5);
  }
  // 瞄准时，调整球杆当前的角度
  public updateDirection(name: string, angle: number) {
    if (!BilliardGame.Instance().actionStage.isInitStage()) return;
    const eulerD: Vec3 = new Vec3(this.qiuganDefault.eulerAngles);// 取模计算
    eulerD.y += 0.03 * angle;
    if (eulerD.y > 360 || eulerD.y < 0) {
      eulerD.y = (eulerD.y + 360) % 360;
    }
    
    this.qiuganDefault.setRotationFromEuler(eulerD);
    if (Game.Instance().isSelftHitBall()) {
      new SendWebSocket().sendAimLine(0,
        this.getEndPointWorldPosition(), this.getDefaultWorldPosition(), this.qiuganDefault.getRotation()
      );
    }
    if (this.refractLine.active) {
      if (databus.hasAimHoleView() && (Game.Instance().currentStep > 0 || Game.Instance().battleCate.isEndlessBattle() || Game.Instance().battleCate.isFancyBattle())) {
        EventManager.Instance().Emit('CameraAimPointDirection', {
          from: BilliardGame.Instance().getVirtualHitBall().worldPosition,
          to: BilliardGame.Instance().getCurrentHitBall().worldPosition,
          lookPoint: BilliardGame.Instance().getCurrentHitBall().worldPosition,
          mbPos: BilliardGame.Instance().getMainWhiteBall().worldPosition,
          aimFlag: 2, cameraHeight: 1.5
        });
      } else {
        this.cameraAimPointDirection(1, 1.5);
      }
    } else {
      this.cameraAimPointDirection(1, 1.5);
    }
  }

  // 球杆向后移动，移动的比例是 percent
  public updateForce(name: string, percent: number) {
    if (BilliardGame.Instance().actionStage.isInitStage()) {
      BilliardGame.Instance().actionStage.directionFinished();
    }
    if (!BilliardGame.Instance().actionStage.isDirectionFinished()) return;
    var div = this.aimDirection();
    const offset = 1 + this.MaxBackDistance * percent;
    const dp = BilliardGame.Instance().getMainWhiteBall().worldPosition;
    this.qiuganDefault.setWorldPosition(
      dp.x - offset * div.x, this.qiuganDefault.getWorldPosition().y, dp.z - offset * div.z,
    );
    this.forceValue = this.MaxForceValue * percent;

    if (Game.Instance().isSelftHitBall()) {
      new SendWebSocket().sendAimLine(0, 
        this.getEndPointWorldPosition(), this.getDefaultWorldPosition(), this.qiuganDefault.getRotation()
      );
    }
  }
  public updateOffsetSpinPosition(name: string, offset: Vec3) {
    this.spinOffsetPos.set(offset);
  }
  public startHitBall(name: string, percent: number) {
    this.forceValue = this.MaxForceValue * percent;
    // 白球以一个力度和速度 + 方向运行
    const dir = this.aimDirection();
    const velocity = new Vec3(dir.x * this.forceValue, 0, dir.z * this.forceValue);
    this.qiuganHitBall(BilliardGame.Instance().getMainWhiteBall().worldPosition, velocity, this.spinOffsetPos);

    WebFrameController.Instance().sendHitMainBall(
      BilliardGame.Instance().getMainWhiteBall().worldPosition, velocity, this.spinOffsetPos,
    );
  }
  public webStartHitBall(name: string, param: NetHitMainBall) {
    this.qiuganHitBall(param.pos as Vec3, param.vel as Vec3, param.sop as Vec3);
  }

  public qiuganHitBall(pos: Vec3, velocity: Vec3, spinOffsetPos: Vec3) {
    BilliardGame.Instance().actionStage.forceFinished();
    BilliardGame.Instance().clearIllegalInfo();
    console.log("start fire white ball xxx ", this.forceValue, this.spinOffsetPos, velocity);
    EventManager.Instance().Emit("WhiteBallHitted", { 
      pos, velocity, spinOffsetPos,
    });
    this.setDefaultWorldPosition(BilliardGame.Instance().getMainWhiteBall().worldPosition);
    // 声音 和 球杆力度大小成 正比例关系
    const percent = Math.sqrt(velocity.x * velocity.x + velocity.z * velocity.z ) / this.MaxForceValue;
    SoundManager.Instance().playHitBall(percent);
    // 先隐藏瞄准线
    this.hideAimCommponent();
    setTimeout(() => {
      EventManager.Instance().Emit('CameraSmoothMoveDirection', {});
      this.hideQiuGanAimCommponent('HideQiuGanAimCommponent', {});
    }, 30);
    BilliardGame.Instance().hideAllOperation();
  }
  // 画瞄准线
  public drawAimLine() {
    if (!BilliardGame.Instance().actionStage.isInitStage()) return;
    const radius = this.whiteBallCollider.radius;
    // 线性速度的方向
    const dir = this.aimDirection();
    const hitInfo = this.calcuColliderTarget(dir, this.whiteBall.worldPosition, radius);
    if (!hitInfo) return;
    this.showAimCommponent();
    BilliardGame.Instance().aimType.aimEmpty();
    // 1. 碰撞的是球
    if (hitInfo.node.name.startsWith('BallSphere')) {
      // 目标球
      const targetBall = hitInfo.node;
      // 打击的目标球
      BilliardGame.Instance().setCurrentHitBall(targetBall);
      BilliardGame.Instance().aimType.aimTargetBall();
      const hitBallPos = this.calcuVirtualHitBalPosition(dir, 
        this.whiteBall.worldPosition, targetBall.worldPosition, this.whiteBallCollider.radius,
      );
      
      this.virtualHitBall.active = true;
      // 设置虚拟碰撞球的位置
      this.virtualHitBall.setWorldPosition(hitBallPos);
      BilliardGame.Instance().setCurrentHitPosition(hitBallPos);
      this.aimLine.setWorldPosition(this.whiteBall.worldPosition);
      this.aimLine.lookAt(hitBallPos);
      const dist: number = Vector3.sub(hitBallPos, this.whiteBall.worldPosition).length();
      this.updateAimLineView(dist);
      const dirv = Vector3.sub(targetBall.worldPosition, hitBallPos);
      
      dirv.normalize();
      BilliardGame.Instance().setRefractVector(dirv);
      const lookPoint = new Vec3(targetBall.worldPosition.x + dirv.x, targetBall.worldPosition.y, targetBall.worldPosition.z + dirv.z);
      // 折射线 目标球 -> 球洞
      this.refractLine.setWorldPosition(targetBall.worldPosition);
      this.refractLine.lookAt(lookPoint);
      this.updateRefractLineView(Game.Instance().getCurrentUserAimLine());
      this.refractLineDirection.set(dirv);
      // 反射线 碰撞后白球的运行轨迹
      const dirref = this.calcuVerticalLine(dirv, dir);
      BilliardGame.Instance().setReflectVector(dirref);
      this.reflectLine.setWorldPosition(this.virtualHitBall.worldPosition);
      const lp = new Vec3(this.virtualHitBall.worldPosition.x + 50 * dirref.x, 0, this.virtualHitBall.worldPosition.z + 50 * dirref.z);
      this.reflectLine.lookAt(lp);
      this.reflectLine.setWorldScale(0.1, 0.1, 0.2);
      return;
    }
    this.hideAimCommponent();
    // 碰不到球
    BilliardGame.Instance().setCurrentHitBall(null);
    const midHitInfo = this.ray(this.whiteBall.worldPosition, dir, QiuGanGontroller.ColliderBallMask);
    if (!midHitInfo) return;
    // 2. 碰撞的是桌边
    if (hitInfo.node.name.startsWith('DeskEdge')) {
      BilliardGame.Instance().aimType.aimDeskEdge();
      this.aimLine.active = true;
      this.aimLine.setWorldPosition(this.whiteBall.worldPosition);
      this.aimLine.lookAt(midHitInfo.hitPoint);
      this.updateAimLineView(midHitInfo.distance);
      const dire = Vector3.sub(midHitInfo.hitPoint, this.whiteBall.worldPosition);
      BilliardGame.Instance().setRefractVector(dire);

      // 反射线 碰撞后白球的运行轨迹
      const dirref = this.calcuVerticalLine(dire, dir);
      BilliardGame.Instance().setReflectVector(dirref);

      BilliardGame.Instance().setCurrentHitPosition(midHitInfo.hitPoint);
      return;
    }
    // 3. 碰撞的是球洞
    if (hitInfo.node.name.startsWith('DeskCornerHole') || hitInfo.node.name.startsWith('DeskCenterHole')) {
      BilliardGame.Instance().aimType.aimTargetHole();
      this.aimLine.active = true;
      this.aimLine.setWorldPosition(this.whiteBall.worldPosition);
      this.aimLine.lookAt(midHitInfo.hitPoint);
      this.updateAimLineView(midHitInfo.distance);
      const dirh = Vector3.sub(midHitInfo.hitPoint, this.whiteBall.worldPosition);
      BilliardGame.Instance().setRefractVector(dirh);

      BilliardGame.Instance().setCurrentHitPosition(midHitInfo.hitPoint);
      return;
    }

    // 3. 碰撞的是deskTop
    if (hitInfo.node.name.startsWith('DeskTop')) {
      console.log('xxxxxxx aimLine desk top ', hitInfo);
      this.aimLine.active = true;
      this.aimLine.setWorldPosition(this.whiteBall.worldPosition);
      this.aimLine.lookAt(hitInfo.hitPoint);
      this.updateAimLineView(hitInfo.distance);
      const dirh = Vector3.sub(hitInfo.hitPoint, this.whiteBall.worldPosition);
      BilliardGame.Instance().setRefractVector(dirh);

      BilliardGame.Instance().setCurrentHitPosition(hitInfo.hitPoint);
      return;
    }
  }

  // 计算虚拟击球点位置
  public calcuVirtualHitBalPosition(dirv: Vec3, startPos: Vec3, targetPos: Vec3, radius: number) {
    // y 方向固定，不用计算
    const a = targetPos.x - startPos.x;
    const b = targetPos.z - startPos.z;
    const deltas = (a * dirv.x + b * dirv.z) * (a * dirv.x + b * dirv.z) - (a * a + b * b - 4 * radius * radius);
    var sqrtdeltas = 0;
    if (deltas > 0) {
      sqrtdeltas = Math.sqrt(deltas);
    }
    const lambda = a * dirv.x + b * dirv.z - sqrtdeltas;
    const hitBallPos = new Vec3(startPos.x + lambda * dirv.x, startPos.y, startPos.z + lambda * dirv.z);
    return hitBallPos;
  }

  // 计算击球的碰撞物体 1 球体 BallSphere  2 桌边DeskEdge  3 球洞DeskHole
  public calcuColliderTarget(dirv: Vec3, startPos: Vec3, radius: number) {
    const y = startPos.y;
    const theta = Math.atan(- dirv.x / dirv.z);
    const lx = startPos.x + radius * Math.cos(theta);
    const lz = startPos.z + radius * Math.sin(theta);
    const lHitInfo = this.ray(new Vec3(lx, y, lz), dirv, QiuGanGontroller.ColliderBallMask);
    
    const rx = startPos.x + radius * Math.cos(theta + Math.PI);
    const rz = startPos.z + radius * Math.sin(theta + Math.PI);
    const rHitInfo = this.ray(new Vec3(rx, y, rz), dirv, QiuGanGontroller.ColliderBallMask);

    if (!lHitInfo && !rHitInfo) return null;
    if (lHitInfo && !rHitInfo) return lHitInfo;
    if (rHitInfo && !lHitInfo) return rHitInfo;
    // 都是球，则可以直接比较距离
    if (this.isBallNamePrefix(lHitInfo.node.name) && this.isBallNamePrefix(rHitInfo.node.name)) {
      if (rHitInfo.distance < lHitInfo.distance) return rHitInfo;
      return lHitInfo;
    } else {
      const mHitInfo = this.ray(startPos, dirv, QiuGanGontroller.ColliderBallMask);
      if (this.isBallNamePrefix(rHitInfo.node.name)) {
        if (!mHitInfo) { return rHitInfo; }
        if (rHitInfo.distance < mHitInfo.distance) return rHitInfo;
      }
      if (this.isBallNamePrefix(lHitInfo.node.name)) {
        if (!mHitInfo) { return lHitInfo; }
        if (lHitInfo.distance < mHitInfo.distance) return lHitInfo;
      }
      return mHitInfo;
    }
    return lHitInfo;
  }
  // 垂直于a，且与b的夹角小于90度
  public calcuVerticalLine(dira: Vec3, dirb: Vec3) {
    var velx = new Vec3(0, 0, 1);
    if (Math.abs(dira.z) >= 0.001) {
      velx.x = 1;
      velx.z =  -dira.x/dira.z;
    }
    velx.normalize();
    if (velx.x * dirb.x + velx.z * dirb.z > 0) {
      return velx;
    }
    var velx = new Vec3(0, 0, -1);
    if (Math.abs(dira.z) >= 0.001) {
      velx.x = -1;
      velx.z = dira.x/dira.z;
    }
    velx.normalize();
    return velx;
  }

  // 只返回射线
  public ray(pos: Vec3, dir: Vec3, mask: number) {
    //射线发射
    const outRay = new geometry.Ray(pos.x, pos.y, pos.z, dir.x, dir.y, dir.z);
    const maxDistance = 10000000;
    const queryTrigger = false;
    
    const bResult = PhysicsSystem.instance.raycastClosest(outRay, mask, maxDistance, queryTrigger);
    if (bResult) {
      const results = PhysicsSystem.instance.raycastClosestResult;
      return {
        collider: results.collider,
        distance: results.distance,
        hitNormal: results.hitNormal,
        hitPoint: results.hitPoint,
        node: results.collider.node,
      }
    }
    return null;
  }
  public hideQiuGanAimCommponent(name: string, data: any) {
    this.node.active = false;
    this.hideAimCommponent();
  }
  public showQiuGanAimCommponent(name: string, data: any) {
    this.node.active = true;
    this.showAimCommponent();
  }
  private hideAimCommponent() {
    this.virtualHitBall.active = false;
    this.aimLine.active = false;
    this.reflectLine.active = false;
    this.refractLine.active = false;
  }
  private showAimCommponent() {
    this.virtualHitBall.active = true;
    this.aimLine.active = true;
    this.reflectLine.active = true;
    this.refractLine.active = true;
  }
  private aimDirection() {
    const dir = Vector3.sub(this.getDefaultWorldPosition(), this.getEndPointWorldPosition());
    dir.y = 0;
    dir.normalize();
    
    return dir;
  }

  private setDefaultWorldPosition(wpos: Vec3) {
    this.qiuganDefault.setWorldPosition(wpos.x, TableAtrribute.DeskTopAxisY + 0.2, wpos.z);
  }
  private getDefaultWorldPosition() {
    this.qiuganDefault.getWorldPosition(this.defaultPointTmpPos);
    this.defaultPointTmpPos.y = TableAtrribute.DeskTopAxisY;
    return this.defaultPointTmpPos;
  }

  private getEndPointWorldPosition() {
    this.qiuganEndPoint.getWorldPosition(this.endPointTmpPos);
    this.endPointTmpPos.y = TableAtrribute.DeskTopAxisY;
    return this.endPointTmpPos;
  }
  private updateAimLineView(dist: number) {
    if (BilliardGame.Instance().watchViewType === 1) { // 2D
      this.aimLine.setWorldScale(0.1, 0.1, dist * 0.1);
    } else {
      this.aimLine.setWorldScale(0.5, 0.1, dist * 0.1);
    }
  }
  private updateRefractLineView(dist: number) {
    if (BilliardGame.Instance().watchViewType === 1) { // 2D
      this.refractLine.setWorldScale(0.1, 0.1, dist);
    } else {
      this.refractLine.setWorldScale(0.5, 0.1, dist);
    }
  }
  private cameraAimPointDirection(af: number, ch: number) {
    const hpos = BilliardGame.Instance().getCurrentHitPosition();
    EventManager.Instance().Emit('CameraAimPointDirection', {
      from: BilliardGame.Instance().getMainWhiteBall().worldPosition,
      to: hpos, lookPoint: hpos, aimFlag: af, cameraHeight: ch,
      mbPos: BilliardGame.Instance().getMainWhiteBall().worldPosition,
    });
  }

  // 插件更新节点
  private updateQiuGan(qiuGanNode: Node) {
    for (const qg of this.node.children) { qg.destroy(); }
    qiuGanNode.setParent(this.node);
    qiuGanNode.active = true;
  }

}

